/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002-2006 * Sleepycat Software. All rights reserved. * * $Id: SecondaryCursor.java,v 1.1 2006/05/06 08:59:28 ckaestne Exp $ */ package com.sleepycat.je; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import com.sleepycat.je.dbi.GetMode; import com.sleepycat.je.dbi.CursorImpl.SearchMode; import com.sleepycat.je.txn.Locker; /** * Javadoc for this public class is generated via * the doc templates in the doc_src directory. */ public class SecondaryCursor extends Cursor { private SecondaryDatabase secondaryDb; private Database primaryDb; /** * Cursor constructor. Not public. To get a cursor, the user should * call SecondaryDatabase.cursor(); */ SecondaryCursor(SecondaryDatabase dbHandle, Transaction txn, CursorConfig cursorConfig) throws DatabaseException { super(dbHandle, txn, cursorConfig); secondaryDb = dbHandle; primaryDb = dbHandle.getPrimaryDatabase(); } /** * Copy constructor. */ private SecondaryCursor(SecondaryCursor cursor, boolean samePosition) throws DatabaseException { super(cursor, samePosition); secondaryDb = cursor.secondaryDb; primaryDb = cursor.primaryDb; } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public Database getPrimaryDatabase() { return primaryDb; } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public Cursor dup(boolean samePosition) throws DatabaseException { checkState(true); return new SecondaryCursor(this, samePosition); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public SecondaryCursor dupSecondary(boolean samePosition) throws DatabaseException { return (SecondaryCursor) dup(samePosition); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus delete() throws DatabaseException { checkState(true); checkUpdatesAllowed("delete"); trace(Level.FINEST, "SecondaryCursor.delete: ", null); /* Read the primary key (the data of a secondary). */ DatabaseEntry key = new DatabaseEntry(); DatabaseEntry pKey = new DatabaseEntry(); OperationStatus status = getCurrentInternal(key, pKey, LockMode.RMW); /* Delete the primary and all secondaries (including this one). */ if (status == OperationStatus.SUCCESS) { status = primaryDb.deleteInternal(cursorImpl.getLocker(), pKey); } return status; } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus put(DatabaseEntry key, DatabaseEntry data) throws DatabaseException { throw SecondaryDatabase.notAllowedException(); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus putNoOverwrite(DatabaseEntry key, DatabaseEntry data) throws DatabaseException { throw SecondaryDatabase.notAllowedException(); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus putNoDupData(DatabaseEntry key, DatabaseEntry data) throws DatabaseException { throw SecondaryDatabase.notAllowedException(); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus putCurrent(DatabaseEntry data) throws DatabaseException { throw SecondaryDatabase.notAllowedException(); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getCurrent(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getCurrent(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getCurrent(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(true); checkArgsNoValRequired(key, pKey, data); trace(Level.FINEST, "SecondaryCursor.getCurrent: ", lockMode); return getCurrentInternal(key, pKey, data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getFirst(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getFirst(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getFirst(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key, pKey, data); trace(Level.FINEST, "SecondaryCursor.getFirst: ", lockMode); return position(key, pKey, data, lockMode, true); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getLast(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getLast(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getLast(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key, pKey, data); trace(Level.FINEST, "SecondaryCursor.getLast: ", lockMode); return position(key, pKey, data, lockMode, false); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getNext(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getNext(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getNext(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key, pKey, data); trace(Level.FINEST, "SecondaryCursor.getNext: ", lockMode); if (cursorImpl.isNotInitialized()) { return position(key, pKey, data, lockMode, true); } else { return retrieveNext(key, pKey, data, lockMode, GetMode.NEXT); } } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getNextDup(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getNextDup(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getNextDup(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(true); checkArgsNoValRequired(key, pKey, data); trace(Level.FINEST, "SecondaryCursor.getNextDup: ", lockMode); return retrieveNext(key, pKey, data, lockMode, GetMode.NEXT_DUP); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getNextNoDup(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getNextNoDup(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getNextNoDup(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key, pKey, data); trace(Level.FINEST, "SecondaryCursor.getNextNoDup: ", null, null, lockMode); if (cursorImpl.isNotInitialized()) { return position(key, pKey, data, lockMode, true); } else { return retrieveNext(key, pKey, data, lockMode, GetMode.NEXT_NODUP); } } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getPrev(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getPrev(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getPrev(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key, pKey, data); trace(Level.FINEST, "SecondaryCursor.getPrev: ", lockMode); if (cursorImpl.isNotInitialized()) { return position(key, pKey, data, lockMode, false); } else { return retrieveNext(key, pKey, data, lockMode, GetMode.PREV); } } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getPrevDup(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getPrevDup(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getPrevDup(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(true); checkArgsNoValRequired(key, pKey, data); trace(Level.FINEST, "SecondaryCursor.getPrevDup: ", lockMode); return retrieveNext(key, pKey, data, lockMode, GetMode.PREV_DUP); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getPrevNoDup(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getPrevNoDup(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getPrevNoDup(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key, pKey, data); trace(Level.FINEST, "SecondaryCursor.getPrevNoDup: ", lockMode); if (cursorImpl.isNotInitialized()) { return position(key, pKey, data, lockMode, false); } else { return retrieveNext(key, pKey, data, lockMode, GetMode.PREV_NODUP); } } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchKey(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getSearchKey(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchKey(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); DatabaseUtil.checkForNullDbt(key, "key", true); DatabaseUtil.checkForNullDbt(pKey, "pKey", false); DatabaseUtil.checkForNullDbt(data, "data", false); trace(Level.FINEST, "SecondaryCursor.getSearchKey: ", key, null, lockMode); return search(key, pKey, data, lockMode, SearchMode.SET); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchKeyRange(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { return getSearchKeyRange(key, new DatabaseEntry(), data, lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchKeyRange(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); DatabaseUtil.checkForNullDbt(key, "key", true); DatabaseUtil.checkForNullDbt(pKey, "pKey", false); DatabaseUtil.checkForNullDbt(data, "data", false); trace(Level.FINEST, "SecondaryCursor.getSearchKeyRange: ", key, data, lockMode); return search(key, pKey, data, lockMode, SearchMode.SET_RANGE); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchBoth(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { throw SecondaryDatabase.notAllowedException(); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchBoth(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); DatabaseUtil.checkForNullDbt(key, "key", true); DatabaseUtil.checkForNullDbt(pKey, "pKey", true); DatabaseUtil.checkForNullDbt(data, "data", false); trace(Level.FINEST, "SecondaryCursor.getSearchBoth: ", key, data, lockMode); return search(key, pKey, data, lockMode, SearchMode.BOTH); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchBothRange(DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { throw SecondaryDatabase.notAllowedException(); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchBothRange(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); DatabaseUtil.checkForNullDbt(key, "key", true); DatabaseUtil.checkForNullDbt(pKey, "pKey", true); DatabaseUtil.checkForNullDbt(data, "data", false); trace(Level.FINEST, "SecondaryCursor.getSearchBothRange: ", key, data, lockMode); return search(key, pKey, data, lockMode, SearchMode.BOTH_RANGE); } /** * Returns the current key and data. */ private OperationStatus getCurrentInternal(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { OperationStatus status = getCurrentInternal(key, pKey, lockMode); if (status == OperationStatus.SUCCESS) { /* * May return KEYEMPTY if read-uncommitted and the primary was * deleted. */ status = readPrimaryAfterGet(key, pKey, data, lockMode); } return status; } /** * Calls search() and retrieves primary data. */ OperationStatus search(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode, SearchMode searchMode) throws DatabaseException { /* * Perform retries to account for deletions during a read-uncommitted. */ while (true) { OperationStatus status = search(key, pKey, lockMode, searchMode); if (status != OperationStatus.SUCCESS) { return status; } status = readPrimaryAfterGet(key, pKey, data, lockMode); if (status == OperationStatus.SUCCESS) { return status; } } } /** * Calls position() and retrieves primary data. */ OperationStatus position(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode, boolean first) throws DatabaseException { /* * Perform retries to account for deletions during a read-uncommitted. */ while (true) { OperationStatus status = position(key, pKey, lockMode, first); if (status != OperationStatus.SUCCESS) { return status; } status = readPrimaryAfterGet(key, pKey, data, lockMode); if (status == OperationStatus.SUCCESS) { return status; } } } /** * Calls retrieveNext() and retrieves primary data. */ OperationStatus retrieveNext(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode, GetMode getMode) throws DatabaseException { /* * Perform retries to account for deletions during a read-uncommitted. */ while (true) { OperationStatus status = retrieveNext(key, pKey, lockMode, getMode); if (status != OperationStatus.SUCCESS) { return status; } status = readPrimaryAfterGet(key, pKey, data, lockMode); if (status == OperationStatus.SUCCESS) { return status; } } } /** * Reads the primary data for a primary key that was read via a secondary. * When SUCCESS is returned by this method, the caller should return * SUCCESS. When KEYEMPTY is returned, the caller should treat this as a * deleted record and either retry the operation (in the case of position, * search, and retrieveNext) or return KEYEMPTY (in the case of * getCurrent). KEYEMPTY is only returned when read-uncommitted is used. * * @return SUCCESS if the primary was read succesfully, or KEYEMPTY if * using read-uncommitted and the primary has been deleted, or KEYEMPTY if * using read-uncommitted and the primary has been updated and no longer * contains the secondary key. * * @throws DatabaseException to indicate a corrupt secondary reference if * the primary record is not found and read-uncommitted is not used (or * read-uncommitted is used, but we cannot verify that a valid deletion has * occured). */ private OperationStatus readPrimaryAfterGet(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode) throws DatabaseException { Locker locker = cursorImpl.getLocker(); Cursor cursor = null; try { cursor = new Cursor(primaryDb, locker, null); OperationStatus status = cursor.search(pKey, data, lockMode, SearchMode.SET); if (status != OperationStatus.SUCCESS) { /* * If using read-uncommitted and the primary is not found, * check to see if the secondary key also has been deleted. If * so, the primary was deleted in between reading the secondary * and the primary. It is not corrupt, so we return KEYEMPTY. */ if (isReadUncommittedMode(lockMode)) { status = getCurrentInternal(key, pKey, lockMode); if (status == OperationStatus.KEYEMPTY) { return status; } } /* Secondary reference is corrupt. */ SecondaryDatabase secDb = (SecondaryDatabase) getDatabase(); throw secDb.secondaryCorruptException(); } /* * If using read-uncommitted and the primary was found, check to * see if primary was updated so that it no longer contains the * secondary key. If it has been, return KEYEMPTY. */ if (isReadUncommittedMode(lockMode)) { SecondaryConfig config = secondaryDb.getPrivateSecondaryConfig(); /* * If the secondary key is immutable, or the key creators are * null (the database is read only), then we can skip this * check. */ if (config.getImmutableSecondaryKey()) { /* Do nothing. */ } else if (config.getKeyCreator() != null) { /* * Check that the key we're using is equal to the key * returned by the key creator. */ DatabaseEntry secKey = new DatabaseEntry(); if (!config.getKeyCreator().createSecondaryKey (secondaryDb, pKey, data, secKey) || !secKey.equals(key)) { return OperationStatus.KEYEMPTY; } } else if (config.getMultiKeyCreator() != null) { /* * Check that the key we're using is in the set returned by * the key creator. */ Set results = new HashSet(); config.getMultiKeyCreator().createSecondaryKeys (secondaryDb, pKey, data, results); if (!results.contains(key)) { return OperationStatus.KEYEMPTY; } } } return OperationStatus.SUCCESS; } finally { if (cursor != null) { cursor.close(); } } } /** * Note that this flavor of checkArgs doesn't require that the * dbt data is set. */ private void checkArgsNoValRequired(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data) { DatabaseUtil.checkForNullDbt(key, "key", false); DatabaseUtil.checkForNullDbt(pKey, "pKey", false); DatabaseUtil.checkForNullDbt(data, "data", false); } }